#include "StdAfx.h"
#include "DSData.h"
#include "CommonData.h"
#include "CommonFun.h"

const int AUDIO=1, VIDEO=2; // Used for enabling playback menu items

CDSData::CDSData(void)
{
	m_bAudioOnly=FALSE;
	m_bFullscreen=FALSE;
	m_lVolume;
	m_dwGraphRegister=0;
	m_psCurrent=Stopped;
	m_PlaybackRate=1.0;

	pGB = NULL;
	pMC = NULL;
	pME = NULL;
	pVW = NULL;
	pBA = NULL;
	pBV = NULL;
	pMS = NULL;
	pMP = NULL;
	pFS = NULL;
}

CDSData::~CDSData(void)
{
}





HRESULT CDSData::PlayMovieInWindow(LPCTSTR szFile)
{
	USES_CONVERSION;
	WCHAR wFile[MAX_PATH];
	HRESULT hr;
	TCHAR ch[MAX_PATH] = {0};
	if (!szFile)
		return E_POINTER;

	wcsncpy(ch,szFile,wcslen(szFile));


	// Clear open dialog remnants before calling RenderFile()
	::UpdateWindow(m_hWnd);

	// Convert filename to wide character string
	wcsncpy(wFile, T2W(ch), NUMELMS(wFile)-1);
	wFile[MAX_PATH-1] = 0;

	// Get the interface for DirectShow's GraphBuilder
	JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
		IID_IGraphBuilder, (void **)&pGB));

	if(SUCCEEDED(hr))
	{
		// Have the graph builder construct its the appropriate graph automatically
		JIF(pGB->RenderFile(wFile, NULL));

		// QueryInterface for DirectShow interfaces
		JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC));
		JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME));
		JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS));
		JIF(pGB->QueryInterface(IID_IMediaPosition, (void **)&pMP));

		// Query for video interfaces, which may not be relevant for audio files
		JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW));
		JIF(pGB->QueryInterface(IID_IBasicVideo, (void **)&pBV));

		// Query for audio interfaces, which may not be relevant for video-only files
		JIF(pGB->QueryInterface(IID_IBasicAudio, (void **)&pBA));

		// Is this an audio-only file (no video component)?
		CheckVisibility();

		// Have the graph signal event via window callbacks for performance
		JIF(pME->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, 0));

		if (!m_bAudioOnly)
		{
			// Setup the video window
			JIF(pVW->put_Owner((OAHWND)m_hWnd));
			JIF(pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN));

			JIF(InitVideoWindow(1, 1,m_hWnd));
			GetFrameStepInterface();
		}
		else
		{
			// Initialize the default player size and enable playback menu items
			JIF(InitPlayerWindow(m_hWnd));
		}

		// Complete window initialization
		///CheckSizeMenu(ID_FILE_SIZE_NORMAL);
		::ShowWindow(m_hWnd, SW_SHOWNORMAL);
		::UpdateWindow(m_hWnd);
		::SetForegroundWindow(m_hWnd);
		m_bFullscreen = FALSE;
		m_PlaybackRate = 1.0;
		//		UpdateMainTitle();
#ifdef REGISTER_FILTERGRAPH
		hr = AddGraphToRot(pGB, &m_dwGraphRegister);
		if (FAILED(hr))
		{
			Msg(TEXT("Failed to register filter graph with ROT!  hr=0x%x"), hr);
			m_dwGraphRegister = 0;
		}
#endif

		// Run the graph to play the media file
		JIF(pMC->Run());

		m_psCurrent=Running;
		::SetFocus(m_hWnd);
	}

	return hr;
}



HRESULT CDSData::InitVideoWindow(int nMultiplier, int nDivider,HWND hWnd)
{
	LONG lHeight, lWidth;
	HRESULT hr = S_OK;
	RECT rect;

	if (!pBV)
		return S_OK;

	// Read the default video size
	hr = pBV->GetVideoSize(&lWidth, &lHeight);
	if (hr == E_NOINTERFACE)
		return S_OK;


	// Account for requests of normal, half, or double size
	lWidth  = lWidth  * nMultiplier / nDivider;
	lHeight = lHeight * nMultiplier / nDivider;

	int nTitleHeight  = GetSystemMetrics(SM_CYCAPTION);
	int nBorderWidth  = GetSystemMetrics(SM_CXBORDER);
	int nBorderHeight = GetSystemMetrics(SM_CYBORDER);

	// Account for size of title bar and borders for exact match
	// of window client area to default video size
	::SetWindowPos(hWnd, NULL, 0, 0, lWidth + 2*nBorderWidth,
		lHeight + nTitleHeight + 2*nBorderHeight,
		SWP_NOMOVE | SWP_NOOWNERZORDER);

	::GetClientRect(hWnd, &rect);
	JIF(pVW->SetWindowPosition(rect.left, rect.top, rect.right, rect.bottom));

	return hr;
}


HRESULT CDSData::InitPlayerWindow(HWND hWnd)
{
	m_hWnd = hWnd;
	// Reset to a default size for audio and after closing a clip
	::SetWindowPos(hWnd, NULL, 0, 0,
		DEFAULT_AUDIO_WIDTH,
		DEFAULT_AUDIO_HEIGHT,
		SWP_NOMOVE | SWP_NOOWNERZORDER);
	m_lVolume = VOLUME_FULL;
	return S_OK;
}


void CDSData::MoveVideoWindow(HWND hWnd)
{
	HRESULT hr;

	// Track the movement of the container window and resize as needed
	if(pVW)
	{
		RECT client;

		::GetClientRect(hWnd, &client);
		hr = pVW->SetWindowPosition(client.left, client.top,
			client.right, client.bottom);
	}
}


void CDSData::CheckVisibility(void)
{
	long lVisible;
	HRESULT hr;

	if ((!pVW) || (!pBV))
	{
		// Audio-only files have no video interfaces.  This might also
		// be a file whose video component uses an unknown video codec.
		m_bAudioOnly = TRUE;
		return;
	}
	else
	{
		// Clear the global flag
		m_bAudioOnly = FALSE;
	}

	hr = pVW->get_Visible(&lVisible);
	if (FAILED(hr))
	{
		// If this is an audio-only clip, get_Visible() won't work.
		//
		// Also, if this video is encoded with an unsupported codec,
		// we won't see any video, although the audio will work if it is
		// of a supported format.
		//
		if (hr == E_NOINTERFACE)
		{
			m_bAudioOnly = TRUE;
		}
		else
		{
			Msg(TEXT("Failed(%08lx) in pVW->get_Visible()!\r\n"), hr);
		}
	}
}




HRESULT CDSData::ToggleFullScreen()
{
	HRESULT hr=S_OK;
	LONG lMode;
	static HWND hDrain=0;

	// Don't bother with full-screen for audio-only files
	if ((m_bAudioOnly) || (!pVW))
		return S_OK;

	// Read current state
	JIF(pVW->get_FullScreenMode(&lMode));

	if (lMode == OAFALSE)
	{
		// Save current message drain
		LIF(pVW->get_MessageDrain((OAHWND *) &hDrain));

		// Set message drain to application main window
		LIF(pVW->put_MessageDrain((OAHWND) m_hWnd));

		// Switch to full-screen mode
		lMode = OATRUE;
		JIF(pVW->put_FullScreenMode(lMode));
		m_bFullscreen = TRUE;
	}
	else
	{
		// Switch back to windowed mode
		lMode = OAFALSE;
		JIF(pVW->put_FullScreenMode(lMode));

		// Undo change of message drain
		LIF(pVW->put_MessageDrain((OAHWND) hDrain));

		// Reset video window
		LIF(pVW->SetWindowForeground(-1));

		// Reclaim keyboard focus for player application
		::UpdateWindow(m_hWnd);
		::SetForegroundWindow(m_hWnd);
		::SetFocus(m_hWnd);
		m_bFullscreen = FALSE;
	}

	return hr;
}


//
// Some video renderers support stepping media frame by frame with the
// IVideoFrameStep interface.  See the interface documentation for more
// details on frame stepping.
//
BOOL CDSData::GetFrameStepInterface(void)
{
	HRESULT hr;
	IVideoFrameStep *pFSTest = NULL;

	// Get the frame step interface, if supported
	hr = pGB->QueryInterface(__uuidof(IVideoFrameStep), (PVOID *)&pFSTest);
	if (FAILED(hr))
		return FALSE;

	// Check if this decoder can step
	hr = pFSTest->CanStep(0L, NULL);

	if (hr == S_OK)
	{
		pFS = pFSTest;  // Save interface to global variable for later use
		return TRUE;
	}
	else
	{
		pFSTest->Release();
		return FALSE;
	}
}

HRESULT CDSData::StepFrames(int nFramesToStep)
{
	HRESULT hr=S_OK;

	// If the Frame Stepping interface exists, use it to step frames
	if (pFS)
	{
		// The renderer may not support frame stepping for more than one
		// frame at a time, so check for support.  S_OK indicates that the
		// renderer can step nFramesToStep successfully.
		if ((hr = pFS->CanStep(nFramesToStep, NULL)) == S_OK)
		{
			// The graph must be paused for frame stepping to work
			if (m_psCurrent != State_Paused)
				PauseClip();

			// Step the requested number of frames, if supported
			hr = pFS->Step(nFramesToStep, NULL);
		}
	}

	return hr;
}


HRESULT CDSData::ModifyRate(double dRateAdjust)
{
	HRESULT hr=S_OK;
	double dRate;

	// If the IMediaPosition interface exists, use it to set rate
	if ((pMP) && (dRateAdjust != 0))
	{
		if ((hr = pMP->get_Rate(&dRate)) == S_OK)
		{
			// Add current rate to adjustment value
			double dNewRate = dRate + dRateAdjust;
			hr = pMP->put_Rate(dNewRate);

			// Save global rate
			if (SUCCEEDED(hr))
			{
				m_PlaybackRate = dNewRate;
				//				UpdateMainTitle();
			}
		}
	}

	return hr;
}


HRESULT CDSData::SetRate(double dRate)
{
	HRESULT hr=S_OK;

	// If the IMediaPosition interface exists, use it to set rate
	if (pMP)
	{
		hr = pMP->put_Rate(dRate);

		// Save global rate
		if (SUCCEEDED(hr))
		{
			m_PlaybackRate = dRate;
			//			UpdateMainTitle();
		}
	}

	return hr;
}


HRESULT CDSData::HandleGraphEvent(void)
{
	LONG evCode;
	LONG_PTR evParam1, evParam2;
	HRESULT hr=S_OK;

	// Make sure that we don't access the media event interface
	// after it has already been released.
	if (!pME)
		return S_OK;

	// Process all queued events
	while(SUCCEEDED(pME->GetEvent(&evCode, &evParam1, &evParam2, 0)))
	{
		// Free memory associated with callback, since we're not using it
		hr = pME->FreeEventParams(evCode, evParam1, evParam2);

		// If this is the end of the clip, reset to beginning
		if(EC_COMPLETE == evCode)
		{
			LONGLONG pos=0;

			// Reset to first frame of movie
			hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
				NULL, AM_SEEKING_NoPositioning);
			if (FAILED(hr))
			{
				// Some custom filters (like the Windows CE MIDI filter)
				// may not implement seeking interfaces (IMediaSeeking)
				// to allow seeking to the start.  In that case, just stop
				// and restart for the same effect.  This should not be
				// necessary in most cases.
				if (FAILED(hr = pMC->Stop()))
				{
					Msg(TEXT("Failed(0x%08lx) to stop media clip!\r\n"), hr);
					break;
				}

				if (FAILED(hr = pMC->Run()))
				{
					Msg(TEXT("Failed(0x%08lx) to reset media clip!\r\n"), hr);
					break;
				}
			}
		}
	}

	return hr;
}




void CDSData::PauseClip(void)
{
	if (!pMC)
		return;

	// Toggle play/pause behavior
	if((m_psCurrent == Paused) || (m_psCurrent == Stopped))
	{
		if (SUCCEEDED(pMC->Run()))
			m_psCurrent = Running;
	}
	else
	{
		if (SUCCEEDED(pMC->Pause()))
			m_psCurrent = Paused;
	}
}


void CDSData::StopClip(void)
{
	HRESULT hr;

	if ((!pMC) || (!pMS))
		return;

	// Stop and reset postion to beginning
	if((m_psCurrent == Paused) || (m_psCurrent == Running))
	{
		LONGLONG pos = 0;
		hr = pMC->Stop();
		m_psCurrent = Stopped;

		// Seek to the beginning
		hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
			NULL, AM_SEEKING_NoPositioning);

		// Display the first frame to indicate the reset condition
		hr = pMC->Pause();
	}
	m_psCurrent = Stopped;

}


void CDSData::CloseInterfaces(void)
{
	HRESULT hr;

	// Relinquish ownership (IMPORTANT!) after hiding video window
	if(pVW)
	{
		hr = pVW->put_Visible(OAFALSE);
		hr = pVW->put_Owner(NULL);
	}

	// Disable event callbacks
	if (pME)
		hr = pME->SetNotifyWindow((OAHWND)NULL, 0, 0);

#ifdef REGISTER_FILTERGRAPH
	if (m_dwGraphRegister)
	{
		RemoveGraphFromRot(m_dwGraphRegister);
		m_dwGraphRegister = 0;
	}
#endif

	m_bAudioOnly = TRUE;
	m_bFullscreen = FALSE;

	// Release and zero DirectShow interfaces
	SAFE_RELEASE(pME);
	SAFE_RELEASE(pMS);
	SAFE_RELEASE(pMP);
	SAFE_RELEASE(pMC);
	SAFE_RELEASE(pBA);
	SAFE_RELEASE(pBV);
	SAFE_RELEASE(pVW);
	SAFE_RELEASE(pFS);
	SAFE_RELEASE(pGB);

}


#ifdef REGISTER_FILTERGRAPH

HRESULT CDSData::AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
{
	IMoniker * pMoniker;
	IRunningObjectTable *pROT;
	if (FAILED(GetRunningObjectTable(0, &pROT)))
	{
		return E_FAIL;
	}

	WCHAR wsz[128];
	HRESULT hr = StringCchPrintfW(wsz, NUMELMS(wsz), L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph,
		GetCurrentProcessId());

	hr = CreateItemMoniker(L"!", wsz, &pMoniker);
	if (SUCCEEDED(hr))
	{
		// Use the ROTFLAGS_REGISTRATIONKEEPSALIVE to ensure a strong reference
		// to the object.  Using this flag will cause the object to remain
		// registered until it is explicitly revoked with the Revoke() method.
		//
		// Not using this flag means that if GraphEdit remotely connects
		// to this graph and then GraphEdit exits, this object registration
		// will be deleted, causing future attempts by GraphEdit to fail until
		// this application is restarted or until the graph is registered again.
		hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,
			pMoniker, pdwRegister);
		pMoniker->Release();
	}

	pROT->Release();
	return hr;
}

void CDSData::RemoveGraphFromRot(DWORD pdwRegister)
{
	IRunningObjectTable *pROT;

	if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
	{
		pROT->Revoke(pdwRegister);
		pROT->Release();
	}
}

#endif


HRESULT CDSData::ToggleMute(void)
{
	HRESULT hr=S_OK;

	if ((!pGB) || (!pBA))
		return S_OK;

	// Read current volume
	hr = pBA->get_Volume(&m_lVolume);
	if (hr == E_NOTIMPL)
	{
		// Fail quietly if this is a video-only media file
		return S_OK;
	}
	else if (FAILED(hr))
	{
		Msg(TEXT("Failed to read audio volume!  hr=0x%x\r\n"), hr);
		return hr;
	}

	// Switch volume levels
	if (m_lVolume == VOLUME_FULL)
		m_lVolume = VOLUME_SILENCE;
	else
		m_lVolume = VOLUME_FULL;

	// Set new volume
	JIF(pBA->put_Volume(m_lVolume));

	//	UpdateMainTitle();
	return hr;
}


HRESULT CDSData::ReadMediaPosition(LONGLONG & Pos)
{
	HRESULT hr;
	//REFERENCE_TIME rtNow;

	if (!pMS)
		return E_FAIL;

	// Read the current stream position
	hr = pMS->GetCurrentPosition(&Pos);
	if (FAILED(hr))
		return hr;
	return S_OK;
}


HRESULT CDSData::ReadMediaDuration(LONGLONG &nDuration)
{
	if (!pMS)
		return E_FAIL;

	HRESULT hr = pMS->GetDuration(&nDuration);
	return hr;
}
